Skip to content

Conversation

@cozminu
Copy link
Contributor

@cozminu cozminu commented May 27, 2025

Changes proposed in this pull request

Context

fixes #3343

Checklist

  • Related issues linked using fixes #number
  • Tests added/updated
  • Make sure that all checks pass
  • Bruno collection updated (if necessary)
  • Documentation issue created with user-docs label (if necessary)
  • OpenAPI specs updated (if necessary)

@cozminu cozminu self-assigned this May 27, 2025
@netlify
Copy link

netlify bot commented May 27, 2025

Deploy Preview for brilliant-pasca-3e80ec canceled.

Name Link
🔨 Latest commit 8e0b9c0
🔍 Latest deploy log https://app.netlify.com/projects/brilliant-pasca-3e80ec/deploys/68efae64e149b30008fa0c41

@github-actions github-actions bot added type: tests Testing related type: source Changes business logic pkg: auth Changes in the GNAP auth package. labels May 27, 2025
@github-actions
Copy link

github-actions bot commented Jun 2, 2025

🚀 Performance Test Results

Test Configuration:

  • VUs: 4
  • Duration: 1m0s

Test Metrics:

  • Requests/s: 42.50
  • Iterations/s: 14.18
  • Failed Requests: 0.00% (0 of 2556)
📜 Logs

> [email protected] run-tests:testenv /home/runner/work/rafiki/rafiki/test/performance
> ./scripts/run-tests.sh -e test "-k" "-q" "--vus" "4" "--duration" "1m"

Cloud Nine GraphQL API is up: http://localhost:3101/graphql
Cloud Nine Wallet Address is up: http://localhost:3100/
Happy Life Bank Address is up: http://localhost:4100/
cloud-nine-wallet-test-backend already set
cloud-nine-wallet-test-auth already set
happy-life-bank-test-backend already set
happy-life-bank-test-auth already set
     data_received..................: 923 kB 15 kB/s
     data_sent......................: 2.0 MB 33 kB/s
     http_req_blocked...............: avg=7.44µs   min=2.36µs   med=5.55µs   max=942.82µs p(90)=6.77µs   p(95)=7.42µs  
     http_req_connecting............: avg=802ns    min=0s       med=0s       max=902.55µs p(90)=0s       p(95)=0s      
     http_req_duration..............: avg=93.45ms  min=7.37ms   med=77.04ms  max=571.08ms p(90)=160.15ms p(95)=185.01ms
       { expected_response:true }...: avg=93.45ms  min=7.37ms   med=77.04ms  max=571.08ms p(90)=160.15ms p(95)=185.01ms
     http_req_failed................: 0.00%  ✓ 0         ✗ 2556
     http_req_receiving.............: avg=96.17µs  min=23.16µs  med=82.81µs  max=4ms      p(90)=121.63µs p(95)=156.46µs
     http_req_sending...............: avg=37.26µs  min=10.43µs  med=28.62µs  max=2.44ms   p(90)=41.63µs  p(95)=57.49µs 
     http_req_tls_handshaking.......: avg=0s       min=0s       med=0s       max=0s       p(90)=0s       p(95)=0s      
     http_req_waiting...............: avg=93.32ms  min=7.22ms   med=76.89ms  max=571.01ms p(90)=160.05ms p(95)=184.86ms
     http_reqs......................: 2556   42.504649/s
     iteration_duration.............: avg=281.73ms min=173.38ms med=267.29ms max=1.09s    p(90)=350.12ms p(95)=381.1ms 
     iterations.....................: 853    14.184846/s
     vus............................: 4      min=4       max=4 
     vus_max........................: 4      min=4       max=4 

@cozminu cozminu requested review from mkurapov and njlie June 2, 2025 12:30
Comment on lines 31 to 35
jest.mock('../access/types', () => ({
isIncomingPaymentAccessRequest: (access: AccessRequest) =>
access.type === 'incoming-payment',
isQuoteAccessRequest: (access: AccessRequest) => access.type === 'quote'
}))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this mock?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

canSkipInteraction function uses them and the purpose of the unit test is to test only the unit and not the deps, but I see there are also benefits from using the unmocked functions. removed the mock

Copy link
Contributor

@njlie njlie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM, just a couple things

})

describe('getByGrant', (): void => {
test('gets access', async () => {
Copy link
Contributor

@njlie njlie Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: this is getting subject, not access

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, fixed

@github-actions github-actions bot added the pkg: backend Changes in the backend package. label Jun 20, 2025
Copy link
Contributor

@mkurapov mkurapov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Few comments!

Comment on lines 223 to 225
400,
GNAPErrorCode.InvalidRequest,
err.message || 'invalid request'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should match GrantErrors to their respective HTTP codes and GNAPErrorCodes.
This is similar to how @oana-lolea maps AccessErrors to the respective code & error codes in her PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

const emptyAccess = (body.access_token?.access?.length ?? 0) === 0

if (emptySubject && emptyAccess) {
throw new Error('subject or access_token required')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this throw a GrantError instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, adapted to throw GrantError

interface IntrospectBody {
access_token: string
access?: AccessItem[]
subjects?: SubjectItem[]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we don't use subjects anywhere (given that we don't pass it in during the introspection request), then its OK to leave out

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

@cozminu cozminu requested a review from mkurapov June 25, 2025 14:08
Copy link
Contributor

@njlie njlie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one suggestion to be applied accross all the places where trx was cast in order to silence a Typescript error.

let appContainer: TestContainer
let accessService: AccessService
let trx: Knex.Transaction
const trx: Knex.Transaction = null as unknown as Knex.Transaction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be healthier to actually initialize trx in the beforeAll block so that the tables get properly truncated when the test is finished, but the editor also doesn't flag a Typescript error. I assume this type casting was done to address the TS error.

beforeAll(async (): Promise<void> => {
	...
	trx = await appContainer.knex.transaction()
})

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, It was a fix to a TS error

Variable 'trx' is used before being assigned.ts(2454)

Using a transaction is tricky because it can deadlock tests because some queries are done within a transaction and others are not. Managed to fix it, but in another manner.

let trx: KnexOrTransaction

beforeAll(async (): Promise<void> => {
	...
	trx = await appContainer.knex
})

Is this ok with you?

let deps: IocContract<AppServices>
let appContainer: TestContainer
let trx: Knex.Transaction
const trx: Knex.Transaction = undefined as unknown as Knex.Transaction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto to prior comment on initializing trx.

let deps: IocContract<AppServices>
let appContainer: TestContainer
let trx: Knex.Transaction
const trx: Knex.Transaction = undefined as unknown as Knex.Transaction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto to prior comment on initializing trx.

let deps: IocContract<AppServices>
let appContainer: TestContainer
let trx: Knex.Transaction
const trx: Knex.Transaction = undefined as unknown as Knex.Transaction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto to prior comment on initializing trx.

let appContainer: TestContainer
let grantService: GrantService
let trx: Knex.Transaction
const trx: Knex.Transaction = null as unknown as Knex.Transaction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto to prior comment on initializing trx.

@@ -0,0 +1,86 @@
import { AccessRequest } from '../access/types'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding these tests! Especially since they cover logic that existed before this PR

let interaction: Interaction
let token: AccessToken
let trx: Knex.Transaction
const trx: Knex.Transaction = null as unknown as Knex.Transaction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto to prior comment on initializing trx.

let client: string
let amtDelivered: bigint
let trx: Knex.Transaction
const trx: Knex.Transaction = null as unknown as Knex.Transaction
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto to prior comment on initializing trx.

client: grant.client,
access: grant.access?.map((item) => accessToGraphql(item)),
access: grant.access?.map((item) => accessToGraphql(item)) || [],
subject: grant.subjects?.map((item) => subjectToGraphql(item)) || [],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grant.subjects only exists if we add it to the withGraphFetched request in the service methods OR we have subjects as a separate "child" resolver under grants, and call the subjectService.getByGrantId directly.

An example of this is the Asset > sendingFee resolver in backend.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed with withGraphFetched('subjects') in the service methods

"Wallet address of the grantee's account."
client: String!
"Details of the access provided by the grant."
access: [Access!]!
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can keep it as [Access!]!, if there is no access it can simply be an empty array.

400,
GNAPErrorCode.InvalidRequest,
'access identifier required'
err instanceof Error ? err.message : ''
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to avoid empty string, let's provide some generic message instead at least. It's less important for the client, its more so for us to find where to debug it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to 'unknown error while checking interaction requirement'

'subject id must be a valid https url'
)
}
if (subject.format != 'uri') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (subject.format != 'uri') {
if (subject.format !== 'uri') {


function validateSubjectRequest(subject: SubjectRequest): void {
try {
if (!subject.id.startsWith('https://')) throw 1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure if @njlie agrees, but I think just checking that it is a valid URL is sufficient, without checking the https://. IMO it can be up to the ASE to determine how strict they want to be with the specifics, like protocol.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the record, I also agree.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

subjectItems: Subject[]
): OpenPaymentsGrant {
return {
access_token: toOpenPaymentsAccessToken(accessToken, accessItems, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

access_token could be undefined now, just like subject

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

let appContainer: TestContainer
let accessService: AccessService
let trx: Knex.Transaction
let trx: TransactionOrKnex
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should remove the import { Knex } from 'knex' import

ctx.body = {
grantId: interaction.grant.id,
access: access.map(toOpenPaymentsAccess),
subjects: subjects.map(toOpenPaymentsSubject),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there can only be one subject for a grant, but the sub_ids can be many. So this can either be a subject object with sub_ids array, (like the Open Payments spec), or we can directly return sub_ids here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same applies for the subject updates in the GraphQL schema

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

changed to subject object with sub_ids array.
I'm not aware of a subject updates.

@github-actions github-actions bot removed the pkg: backend Changes in the backend package. label Jul 28, 2025
properties:
access:
$ref: ./auth-server.yaml#/components/schemas/access
subject:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make this spec standalone, and move over the correct components (access/subject) into this file, similar to how it was done for token-introspection

@github-actions github-actions bot added pkg: backend Changes in the backend package. pkg: frontend Changes in the frontend package. pkg: mock-ase labels Oct 1, 2025
@cozminu cozminu requested a review from mkurapov October 1, 2025 12:38
Comment on lines 363 to 364
const accessToken = await accessTokenService.create(grant.id)
const access = await accessService.getByGrant(grant.id)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should only create an accessToken if the access_token was provided in the initial grant request. We can use length of access to verify that, I believe. Otherwise, if the client is just requesting subject information, and not access_token, we would return an access_token still, eg:

{
  "access_token": {
    "access": [],
    "value": "EC6884A68394DAC1E186",
    "manage": "http://localhost:3006/token/965841c8-1671-4174-944d-56778083dfdb",
    "expires_in": 600
  },
  "continue": {
    "access_token": {
      "value": "B785F0F5ED51E08A923E"
    },
    "uri": "http://localhost:3006/continue/b094d601-cd62-43fd-9389-5ed6628c1c1d"
  },
  "subject": {
    "sub_ids": [
      {
        "id": "https://cloud-nine-wallet-backend/accounts/gfranklin",
        "format": "uri"
      }
    ]
  }
}

CC @njlie to confirm as well

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah agreed, there's nothing to access, hence no access token.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed, access_token is no more when the grant doesn't contain access items

@github-actions github-actions bot added the type: ci Changes to the CI label Oct 14, 2025
cozminu and others added 6 commits October 14, 2025 15:26
* feat(localenv): expose subject during consent in mock-ase

* feat: include client name in subject grant line

* fix(mase): grantId not being retrieved

* fix(mase): consent and confirmation texts

* fix: handle subject-only grants properly

---------

Co-authored-by: Cozmin Ungureanu <[email protected]>
@github-actions github-actions bot removed pkg: backend Changes in the backend package. pkg: frontend Changes in the frontend package. labels Oct 15, 2025
@cozminu cozminu requested a review from mkurapov October 16, 2025 07:21
Copy link
Contributor

@mkurapov mkurapov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The auth server changes look good 👍
I found a few things in the mock ase, but I will put up a change separately

@cozminu cozminu merged commit 539ced6 into main Oct 20, 2025
44 checks passed
@cozminu cozminu deleted the cozmin/raf-996 branch October 20, 2025 10:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pkg: auth Changes in the GNAP auth package. pkg: mock-ase type: ci Changes to the CI type: source Changes business logic type: tests Testing related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add subject field to Authorization Server

4 participants